package java_thinking.unit_21._21_13;

import java_thinking.unit_21._21_10.DollarAmount;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import static java.util.concurrent.TimeUnit.NANOSECONDS;

/**
 * @Desc:   13-3 通过 tryLock 来避免锁顺序死锁
 * @author: cww
 * @DateTime: 2019/6/26 9:00
 */

public class DeadlockAvoidance {
    private static Random rnd = new Random();
    private static final int DELAY_FIXED = 1;
    private static final int DELAY_RANDOM = 2;

    public boolean transferMoney(Account fromAcct, Account toAcct, DollarAmount amount,
                                 long timeout, TimeUnit unit) throws InterruptedException {
        long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
        long randMod = getRandomDelayModulusNanos(timeout, unit);
        long stopTime = System.nanoTime() + unit.toNanos(timeout);
        while (true) {
            /**
             * 使用 tryLock 来获取两个锁，如果不能同时获得，那么就回退并重新尝试，在休眠时间中包括固定部分和随机部分，
             * 从而降低发生活锁的可能性
             */
            if (fromAcct.lock.tryLock()) {
                try {
                    if (toAcct.lock.tryLock()) {
                        if (fromAcct.getBalance().compareTo(amount) < 0 ) {
                            throw new InsufficientFundsException();
                        } else {
                            fromAcct.debit(amount);
                            toAcct.credit(amount);
                            return true;
                        }
                    }
                } catch (InsufficientFundsException e) {
                    e.printStackTrace();
                } finally {
                    toAcct.lock.unlock();
                }
            }
            /**
             * 如果在执行时间内不能获得所需要的锁，那么返回一个失败状态
             */
            if (System.nanoTime() < stopTime) {
                return false;
            }
            NANOSECONDS.sleep(fixedDelay + rnd.nextLong() & randMod);
        }
    }

    static long getFixedDelayComponentNanos(long timeout, TimeUnit unit) {
        return DELAY_FIXED;
    }

    static long getRandomDelayModulusNanos(long timeout, TimeUnit unit) {
        return DELAY_RANDOM;
    }

    class Account {
        public Lock lock;

        void debit(DollarAmount d) {
        }

        void credit(DollarAmount d) {
        }

        DollarAmount getBalance() {
            return null;
        }
    }

    class InsufficientFundsException extends Exception {
    }
}
